#include <Windows.h>
#include "application.h"
#include "backends/sweety_Impl_Glfw.h"
#include "backends/sweety_Impl_Oengl3.h"
#include "framework/sweety_Scene.h"
#include "components/gl/sweety_FrameBuffer.h"
#include "tools/sweety_Input.h"
#include "glfw/include/glfw3.h"
#include <gl/GL.h>


// [Win32] Our example includes a copy of glfw3.lib pre-compiled with VS2010 to maximize ease of testing and compatibility with old VS compilers.
// To link with VS2010-era libraries, VS2015+ requires linking with legacy_stdio_definitions.lib, which we do using this pragma.
// Your own project should not be affected, as you are likely to link with a newer binary of GLFW that is adequate for your version of Visual Studio.
#if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(SW_DISABLE_WIN32_FUNCTIONS)
#pragma comment(lib, "legacy_stdio_definitions")
#endif

namespace SW
{
	struct GlfwWindowImplData_t
	{
		int			width;
		int			height;
		std::string title;
		SwVec4	clear_color;
		GLFWwindow* window;
	};

	Application::Application()
	{
		if (nullptr == m_MainWindowData)
		{
			m_MainWindowData = new GlfwWindowImplData_t();
			m_MainWindowData->width = 800;
			m_MainWindowData->height = 600;
			m_MainWindowData->title = "Sweety";
		}

		if (nullptr == m_SceneMenu)
		{
			m_SceneMenu = new SwSceneMenu();
		}

		if (nullptr == m_Framebuffer)
		{
			m_Framebuffer = new FrameBuffer();
		}
	}

	Application::Application(int w, int h, const std::string& title)
	{
		if (nullptr == m_MainWindowData)
		{
			m_MainWindowData = new GlfwWindowImplData_t();
			m_MainWindowData->width = w;
			m_MainWindowData->height = h;
			m_MainWindowData->title = title;
		}

		if (nullptr == m_SceneMenu)
		{
			m_SceneMenu = new SwSceneMenu();
		}
	}

	Application::~Application()
	{
		if (m_MainWindowData)
		{
			delete m_MainWindowData;
			m_MainWindowData = nullptr;
		}

		if (m_SceneMenu)
		{
			delete m_SceneMenu;
			m_SceneMenu = nullptr;
		}

		if (m_Framebuffer)
		{
			delete m_Framebuffer;
			m_Framebuffer = nullptr;
		}
	}

	static void glfw_error_callback(int error, const char* description)
	{
		fprintf(stderr, "GLFW Error %d: %s\n", error, description);
	}

	Application* Application::GetInstance()
	{
		SW_ASSERT(m_pSharedApplication);
		return (Application*)m_pSharedApplication;
	}

	bool Application::Init()
	{
		SW_ASSERT(m_MainWindowData);

		if (InitGlfw()) 
		{
			if (InitSwGui()) 
			{
				m_Framebuffer->Init(m_MainWindowData->width, m_MainWindowData->height);
				return true;
			}
		}

		return false;
	}

	void Application::MessageLoop()
	{
		while (!glfwWindowShouldClose(m_MainWindowData->window))
		{
			Update();

			// pool events
			glfwPollEvents();

			Render();

			// Double buffering
			glfwSwapBuffers(m_MainWindowData->window);			
		}

		DeInitSwGui();
		DeInitGlfw();
	}

	void Application::Render()
	{
		ResetSwGui();

		// render scene
		if (m_SceneMenu)
		{
			m_Framebuffer->Bind();
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
			glViewport(0, 0, m_Framebuffer->GetWidth(), m_Framebuffer->GetHeight());			
			glEnable(GL_DEPTH_TEST);
			m_SceneMenu->Render();
			m_Framebuffer->Unbind();
		}

		// render SwGui
		RenderSwGui();
	}

	bool Application::InitGlfw()
	{
		glfwSetErrorCallback(glfw_error_callback);
		if (!glfwInit())
		{
			return false;
		}

#ifdef __EMSCRIPTEN__
		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

		//glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API) ;
#else       
		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
#ifdef GLAPP_REQUIRE_OGL45
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
#else
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
#endif
#endif    
		glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
		glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
		glfwWindowHint(GLFW_DOUBLEBUFFER, GL_TRUE);

		m_MainWindowData->window = glfwCreateWindow(m_MainWindowData->width, m_MainWindowData->height, m_MainWindowData->title.c_str(), NULL, NULL);
		if (nullptr == m_MainWindowData->window)
		{
			glfwTerminate();
			return false;
		}

		glfwMakeContextCurrent(m_MainWindowData->window);
		glfwSwapInterval(1); // fps:60

		Input::SetWindow(m_MainWindowData->window);
		return true;
	}

	bool Application::InitSwGui()
	{
		SW::CreateContext();

		SwGuiIO& io = SW::GetIO(); (void)io;
		io.ConfigFlags |= SwGuiConfigFlags_NavEnableKeyboard;       // Enable Keyboard Controls
		io.ConfigFlags |= SwGuiConfigFlags_NavEnableGamepad;        // Enable Gamepad Controls
		io.ConfigFlags |= SwGuiConfigFlags_DockingEnable;           // Enable Docking
		io.ConfigFlags |= SwGuiConfigFlags_ViewportsEnable;         // Enable Multi-Viewport / Platform Windows

		//SW::StyleColorsDark();

		// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
		SwGuiStyle& style = SW::GetStyle();
		if (io.ConfigFlags & SwGuiConfigFlags_ViewportsEnable)
		{
			style.WindowRounding = 0.0f;
			style.Colors[SwGuiCol_WindowBg].w = 1.0f;
		}

		// Setup Platform/Renderer backends
		SwGui_ImplGlfw_InitForOpenGL(m_MainWindowData->window, true);

		const char* glsl_version = "#version 410";
		SwGui_ImplOpenGL3_Init(glsl_version);

		return true;
	}

	void Application::DeInitGlfw()
	{
		glfwDestroyWindow(m_MainWindowData->window);
		glfwTerminate();
	}

	void Application::DeInitSwGui()
	{
		SwGui_ImplOpenGL3_Shutdown();
		SwGui_ImplGlfw_Shutdown();
		SW::DestroyContext();
	}

	void Application::ResetSwGui()
	{
		SW_ASSERT(m_MainWindowData);

		SwVec4 clear_color = m_MainWindowData->clear_color;
		int display_w, display_h;
		glfwGetFramebufferSize(m_MainWindowData->window, &display_w, &display_h);
		glViewport(0, 0, display_w, display_h);
		glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
		glClear(GL_COLOR_BUFFER_BIT);
	}

	void Application::RenderSwGui()
	{
		RenderSwGuiBegin();

		if (m_SceneMenu)
		{
			m_SceneMenu->RenderSwGui();
		}

		RenderSwGuiEnd();
	}

	void Application::RenderSwGuiBegin()
	{
		// Opengl3 & Glfw new frame
		SwGui_ImplOpenGL3_NewFrame();
		SwGui_ImplGlfw_NewFrame();

		// Start the SwUI frame
		SW::NewFrame();

		// docking place
		SW::DockSpaceOverViewport();
	}

	void Application::RenderSwGuiEnd()
	{
		// End the SwUI frame
		SW::EndFrame();

		// Render
		SW::Render();
		SwGui_ImplOpenGL3_RenderDrawData(SW::GetDrawData());

		// Update and Render additional Platform Windows
		// (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere.
		//  For this specific demo app we could also call glfwMakeContextCurrent(window) directly)
		SwGuiIO& io = SW::GetIO();
		if (io.ConfigFlags & SwGuiConfigFlags_ViewportsEnable)
		{
			GLFWwindow* backup_current_context = glfwGetCurrentContext();
			SW::UpdatePlatformWindows();
			SW::RenderPlatformWindowsDefault();
			glfwMakeContextCurrent(backup_current_context);
		}
	}

	void Application::SetBackGroundColor(const SwVec4& color)
	{
		SW_ASSERT(m_MainWindowData);
		m_MainWindowData->clear_color = color;
	}

	SwVec2 Application::GetInitializationSize() const
	{
		SW_ASSERT(m_MainWindowData);
		return SwVec2(m_MainWindowData->width, m_MainWindowData->height);
	}

	void Application::UpdateTime()
	{
		static bool first = true;
		if (first)
		{
			m_CurrentTime = glfwGetTime();
			m_LastTime = m_CurrentTime;
			m_DeltaTime = 0.0f;
			first = false;
			return;
		}
		m_CurrentTime = glfwGetTime();
		m_DeltaTime = m_CurrentTime - m_LastTime;
		m_LastTime = m_CurrentTime;
	}

	void Application::Update()
	{
		UpdateTime();
		if (m_SceneMenu)
		{
			m_SceneMenu->Update(m_DeltaTime);
		}
	}

}
